package tasky.ui;

import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;

import org.junit.Assert;

/**
 * This Class contains a JTextPane which is used for inputing texts in Tasky.
 * 
 * This Class contains different methods which controls how texts should be
 * displayed onto the JTextPane component
 * 
 * @author Keith Lim Yong Ming A0097846J
 * 
 */

public class UserInputWindow implements TextWindowInterface {

	private JTextPane jTextf;
	private TextPaneWrapper customWrapper;
	private BaseWindow sourceFrame;

	private String lastCommand;

	private int CARET_TOPLINE;
	private int CARET_BOTLINE;

	private int MIN_NO_OF_INPUT = 0;
	private int MIN_COLOR_INFO = 2;
	private int MIN_NO_OF_SPLITS = 2;

	private static final boolean BOOL_EDITABLE = true;

	private static final String KEYWORD_CLEAR = "clear";
	private static final String KEYWORD_CLEAR2 = "clr";
	private static final String KEYWORD_COLOR = "color";
	private static final String KEYWORD_SPLIT = " ";
	private static final String KEYWORD_BLANK = "";
	private static final String KEYWORD_NEWLINE = "\n";
	private static final String KEYWORD_CARRAIGE = "\r";

	private static final String LOG_INVALIDCARET = "Invalid Location for Input Caret!";
	private static final String LOG_INITIALIZE = "Initialize TextWindow Logger";
	private static final String LOG_USERINPUT = "User Typed ";
	private static final String LOG_CLEARINPUT = "Clear Input was ";
	private static final String LOG_NAME = "UserInputWindowLog.log";

	private static final String NAME_USERSTYLE = "userStyle";

	private Stack<String> prevCommands;
	private Stack<String> nextCommands;

	private Logger userInputLogger;

	public UserInputWindow(BaseWindow sF) {
		loadLog();

		instantiateObjects();

		setTextBoxEditor();

		assignFrame(sF);

		setTextBoxEditable();

		prepareWriting();
	}

	private void assignFrame(BaseWindow sF) {
		sourceFrame = sF;
	}

	private void setTextBoxEditor() {
		jTextf.setEditorKit(customWrapper);
	}

	private void instantiateObjects() {
		jTextf = new JTextPane();

		prevCommands = new Stack<String>();

		nextCommands = new Stack<String>();

		lastCommand = "";

		customWrapper = new TextPaneWrapper();
	}

	private void loadLog() {
		try {
			userInputLogger = Logger.getLogger(LOG_NAME);
			FileHandler fh = null;
			fh = new FileHandler(LOG_NAME);
			userInputLogger.addHandler(fh);
			SimpleFormatter formatter = new SimpleFormatter();
			fh.setFormatter(formatter);

			userInputLogger.info(LOG_INITIALIZE);

		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void setTextBoxEditable() {
		jTextf.setEditable(BOOL_EDITABLE);
	}

	private void prepareWriting() {

		StyledDocument autoCompleteDocument = prepareAutoDoc();

		prepareTextInputDetails(autoCompleteDocument);

	}

	private void prepareTextInputDetails(StyledDocument autoCompleteDocument) {
		// Set the auto completing document as the document model on our input
		// field.
		jTextf.setDocument(autoCompleteDocument);

		jTextf.requestFocusInWindow();

		jTextf.addKeyListener(inputKeyListener());
	}

	private StyledDocument prepareAutoDoc() {
		AutoCommand nameService = new AutoCommand();

		// Create the auto completing document model with a reference to the
		// service and the input field.
		return (StyledDocument) new AutoCompleteDocument(nameService, jTextf);
	}

	public void prepareCaretInfo() {
		CARET_BOTLINE = CARET_TOPLINE = getMyCaret().y;
	}

	public void getLowestY() {
		CARET_BOTLINE = getMyCaret().y;
	}

	private KeyListener inputKeyListener() {
		return new TaskyKeyListener();
	}

	private String[] formatUserChoice(String userChoice) {
		return userChoice.split(KEYWORD_SPLIT, MIN_NO_OF_SPLITS);
	}

	private Rectangle getMyCaret() {
		try {
			return jTextf.modelToView(jTextf.getCaretPosition());
		} catch (BadLocationException e) {
			userInputLogger.warning(LOG_INVALIDCARET);
			e.printStackTrace();
		}
		return null;
	}

	private String getInput() {
		String enteredKey = jTextf.getText();
		enteredKey = enteredKey.replace(KEYWORD_NEWLINE, KEYWORD_BLANK)
				.replace(KEYWORD_CARRAIGE, KEYWORD_BLANK);

		return enteredKey;
	}

	public JTextPane getTextWindow() {
		return jTextf;
	}

	public boolean setTheFocus() {
		return jTextf.requestFocusInWindow();
	}

	public void addUserText(String newMsg) {
		try {
			userInputLogger.info(LOG_USERINPUT + newMsg);

			StyledDocument doc = jTextf.getStyledDocument();
			Style userStyle = jTextf.addStyle(NAME_USERSTYLE, null);
			doc.insertString(doc.getLength(), newMsg, userStyle);
		} catch (BadLocationException exc) {
			exc.printStackTrace();
		}
	}

	public void clearAllMsg() {
		jTextf.setText(KEYWORD_BLANK);
	}

	/**
	 * This Class creates a Listener which detects for specific key-presses when
	 * the JTextPane of this class is in focus
	 * 
	 * @author Keith Lim Yong Ming A0097846J
	 * 
	 */

	private class TaskyKeyListener implements KeyListener {
		@Override
		public void keyTyped(KeyEvent e) {

		}

		@Override
		public void keyReleased(KeyEvent e) {
			userReleaseEnter(e);
		}

		private void userReleaseEnter(KeyEvent e) {
			if (keyIsEnter(e)) {
				clearAllMsg();
				prepareCaretInfo();
				sourceFrame.scrollToBottom();
			} else {
				if (isCaretAtLastLine()) {
					setLowestCaretPosition();
				}
			}
		}

		private void setLowestCaretPosition() {
			CARET_BOTLINE = getMyCaret().y;
		}

		private boolean isCaretAtLastLine() {
			return CARET_BOTLINE < getMyCaret().y;
		}

		private boolean keyIsEnter(KeyEvent e) {
			return e.getKeyCode() == KeyEvent.VK_ENTER;
		}

		@Override
		public void keyPressed(KeyEvent e) {

			if (keyIsEnter(e)) {
				try {
					String enteredKey = getInput();

					recordPrevCommand(enteredKey);

					String[] userInput = formatUserChoice(enteredKey);

					deduceCommand(enteredKey, userInput);

					removeNextCommands();

					clearAllMsg();

				} catch (Exception e1) {
					clearAllMsg();
					sourceFrame.passError(e1.getMessage());
				}

			} else if (keyIsUp(e)) {

				if (isCaretOnTopLine()) {
					if (isTherePrevCommands()) {
						loadPrevCommand();
					}
				}
			} else if (keyIsDown(e)) {

				if (isCaretOnBotLine()) {

					if (isThereNextCommands()) {
						loadNextCommand();
					}
				}
			}
		}

		private void loadNextCommand() {
			lastCommand = getNextCommand();
			recordPrevCommand(getInput());
			clearAllMsg();
			addUserText(lastCommand);
			getLowestY();
		}

		private String getNextCommand() {
			return nextCommands.pop();
		}

		private boolean isThereNextCommands() {
			return !nextCommands.isEmpty();
		}

		private boolean isCaretOnBotLine() {
			return CARET_BOTLINE == getMyCaret().y;
		}

		private boolean keyIsDown(KeyEvent e) {
			return e.getKeyCode() == KeyEvent.VK_DOWN;
		}

		private void loadPrevCommand() {
			lastCommand = getPrevCommand();
			recordNextCommand();
			clearAllMsg();
			addUserText(lastCommand);
			getLowestY();
		}

		private String getPrevCommand() {
			return prevCommands.pop();
		}

		private void recordNextCommand() {
			nextCommands.push(getInput());
		}

		private boolean isTherePrevCommands() {
			return !prevCommands.isEmpty();
		}

		private boolean isCaretOnTopLine() {
			return CARET_TOPLINE == getMyCaret().y;
		}

		private boolean keyIsUp(KeyEvent e) {
			return e.getKeyCode() == KeyEvent.VK_UP;
		}

		private void deduceCommand(String enteredKey, String[] userInput)
				throws Exception {
			if (didUserEnterText(userInput)) {

				if (userEnterClear(userInput)) {

					runClearCommand(enteredKey);

				} else if (correctColorInput(userInput)) {

					runColorCommand(userInput);

				} else {

					passCommandToLogic(enteredKey);

				}
			}
		}

		private void removeNextCommands() {
			nextCommands.clear();
		}

		private void passCommandToLogic(String enteredKey) {
			// The other End must NOT be null. Command has to go somewhere
			Assert.assertNotNull(sourceFrame);

			sourceFrame.passResult(enteredKey);
		}

		private void runColorCommand(String[] userInput) throws Exception {
			sourceFrame.changeColor(userInput[1]);
		}

		private void runClearCommand(String enteredKey) {
			userInputLogger.info(LOG_CLEARINPUT + enteredKey);

			// The other End must NOT be null. Command has to go somewhere
			Assert.assertNotNull(sourceFrame);

			sourceFrame.clearDisplay();
		}

		private boolean correctColorInput(String[] userInput) {
			return userInput[0].equals(KEYWORD_COLOR)
					&& userInput.length >= MIN_COLOR_INFO;
		}

		private boolean userEnterClear(String[] userInput) {
			return userInput[0].equals(KEYWORD_CLEAR) || userInput[0].equals(KEYWORD_CLEAR2);
		}

		private boolean didUserEnterText(String[] userInput) {
			return userInput.length > MIN_NO_OF_INPUT;
		}

		private void recordPrevCommand(String enteredKey) {
			prevCommands.push(enteredKey);
		}
	}

	/**
	 * This private Class is part of auto-completing user's input
	 * 
	 * @author Keith Lim Yong Ming A0097846J
	 * 
	 */
	private static class AutoCommand implements AutoCompleteInterface<String> {

		private List<String> data;

		private static final String KEYWORD_ADD = "add";
		private static final String KEYWORD_DELETE = "delete";
		private static final String KEYWORD_EDIT = "edit";
		private static final String KEYWORD_DISPLAY = "display";
		private static final String KEYWORD_COLOR = "color";
		private static final String KEYWORD_DONE = "done";
		private static final String KEYWORD_UNDONE = "undone";
		private static final String KEYWORD_UNDO = "undo";
		private static final String KEYWORD_HISTORY = "history";
		private static final String KEYWORD_REDO = "redo";
		private static final String KEYWORD_EXIT = "exit";
		private static final String KEYWORD_SEARCH = "search";
		private static final String KEYWORD_CLEAR = "clear";
		private static final String KEYWORD_EXPORT = "export";

		private static final String ALT_ADD = "create";
		private static final String ALT_CLEAR = "clr";
		private static final String ALT_DELETE = "del";
		private static final String ALT_EXIT = "quit";
		private static final String ALT_SEARCH = "find";

		
		
		
		public AutoCommand() {
			data = Arrays.asList(KEYWORD_ADD, KEYWORD_DELETE, KEYWORD_EDIT,
					KEYWORD_DISPLAY, KEYWORD_COLOR, KEYWORD_DONE,
					KEYWORD_UNDONE, KEYWORD_HISTORY, KEYWORD_REDO,
					KEYWORD_UNDO, KEYWORD_EXIT, KEYWORD_SEARCH, KEYWORD_CLEAR, 
					KEYWORD_EXPORT, ALT_ADD, ALT_CLEAR, ALT_DELETE, ALT_EXIT, ALT_SEARCH);
		}

		@Override
		public String toString() {
			StringBuilder b = new StringBuilder();
			for (String o : data) {
				b.append(o).append(KEYWORD_NEWLINE);
			}
			return b.toString();
		}

		public String autoCompleteStatement(String startsWith) {

			String match = null;
			for (String o : data) {
				if (o.startsWith(startsWith)) {
					if (match == null) {
						match = o;
					} else {
						match = null;
						break;
					}
				}
			}
			return match;
		}

	}

}
